package A3JGroupsGC;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.jgroups.Address;
import org.jgroups.JChannel;
import org.jgroups.View;

import A3GCPackage.NodeRanker;
import A3GCPackage.SingleNodeData;
import A3JGroups.A3JGMessage;
import A3JGroups.JGSupervisorRole;

public abstract class GCSupervisorRole extends JGSupervisorRole
{
	protected NodeRanker   nodeRanker;
	protected JChannel     nodeRecyclerChannel;
	protected final String gcChannel = "A3GCPackageChannel";
	protected String       properties = null;
	protected View         view;
	
	public GCSupervisorRole(int resourceCost, String groupName) 
	{
		super(resourceCost, groupName);
		
		try 
		{
			nodeRecyclerChannel = new JChannel(properties);
			nodeRanker = new NodeRanker(nodeRecyclerChannel);
			nodeRecyclerChannel.connect("A3GCPackageChannel");
		
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}

	public void dispatchToFollower( A3JGMessage mex )
	{
		// get available node
		SingleNodeData snd = nodeRanker.getAvailableNode( getGroupName() );
		if ( snd == null )
		{
			// this is the case when there isn't an available node
			noNodeAvailable( mex );
			return;
		}
		
		// is this current group?
		if ( snd.group.compareTo( getGroupName() ) != 0 )
		{
			// if no, change node's group
			A3JGMessage msg = new A3JGMessage();
			msg.setContent("A3JoinGroup"+getGroupName());
			List<Address> d = new ArrayList<Address>();
			d.add( snd.address );
			// This doesn't work because the framework doesn't support 
			// sending the A3JoinGroup to another group node
			sendMessageToFollower(mex, d);

		}
		
		// set node activity to active
		nodeRanker.setNodeActivity( snd.name, true, getGroupName(), snd.address );
		
		// translate message (call to concrete class)
		A3JGMessage out = translateMessage(mex, snd );
		
		System.out.println("GCSupervisor[" + this.getNode().getID() + "," + getChan().getAddress().toString() + "]: dispatchToFollower: object is <" + out.getContent().toString() + "> addr is <" + snd.address + ">" );
		
		// sendMessageToFollower
		List<Address> dest = new ArrayList<Address>();
		System.out.println("GCSupervisor[" + this.getNode().getID() + "," + getChan().getAddress().toString() + "]: dispatchToFollower:snd.address is " + snd.address.toString() );
		dest.add( snd.address );
		sendMessageToFollower(out, dest);
		
	}
	
	public void messageFromFollower( A3JGMessage msg )
	{
		// this is client processing - call dispatch to follower
		System.out.println("GCSupervisor[" + this.getNode().getID() + "," + getChan().getAddress().toString() + "]: messageFromFollower - got a message from someone <" + msg.getContent().toString() + ">" );
		dispatchToFollower( msg );
		// idea here is to allow the concrete class to override this method but still be able to 
		// use dispatchToFollower if desired
	}
	
	public void updateFromFollower( A3JGMessage msg )
	{
		
		System.out.println("GCSupervisor[" + this.getNode().getID() + "," + getChan().getAddress().toString() + "]: updateFromFollower - got a response" );
		
		// pass message back to client
		String straddr = passMessageToClient(msg);
		if ( straddr == null )
			return ;
		
		View curview = getChan().getView();
		
		Iterator<Address> iter = curview.iterator();
		
		Address retaddr = null;
		while ( iter.hasNext() )
		{
			Address addr = iter.next();
			
			if ( addr.toString().compareTo(straddr) == 0 )
			{
				// match found 
				retaddr = addr;
				break;
			}
		}
		
		if ( retaddr == null ) // lost client
			return;
		
		// set Node's activity to not active
		nodeRanker.setNodeActivity(straddr, false, getGroupName(), retaddr);
		
		// any cleanup
	}
	
	public abstract A3JGMessage translateMessage( A3JGMessage mex, SingleNodeData snd );
	
	public abstract String passMessageToClient( A3JGMessage mex );

	public abstract void noNodeAvailable( A3JGMessage mex );
	
	public void viewAccepted( View view )
	{
		List<Address> list = Collections.synchronizedList( new ArrayList<Address>( view.getMembers() ) );
		List<Address> olist;
		if ( this.view == null )
			olist = Collections.synchronizedList(new ArrayList<Address>());
		else
			olist = Collections.synchronizedList( new ArrayList<Address>( this.view.getMembers() ) );
		
		System.out.println("viewAccepted has been called in GCSupervisor");
		System.out.println("view is now: " );
		System.out.println(view);
		
		// O(n^2) process - should look for way to compute this quicker
		Iterator<Address> iter1 = list.iterator();
		
		// this loop checks if there are new nodes to add
		while ( iter1.hasNext() )
		{
			Address addr = iter1.next();
			
			// do not allow the addition of client nodes
			if ( !( addr.toString().startsWith("Client") ) )
			{
				boolean flag = false;
				Iterator<Address> iter2 = olist.iterator();
				while ( iter2.hasNext() )
				{
					Address addr2 = iter2.next();
					if ( addr.compareTo(addr2) == 0 )
					{
						// remove addr2 from olist - saves some searching time
						olist.remove(addr2);
						flag = true;
						break;
					}
				}
				if ( flag == false )
				{
					// final check - don't allow the supervisor to enter the map
					if ( getChan().getAddress().compareTo(addr) != 0 )
					{		
						
						// need to add it to nodeRanker
						nodeRanker.setNodeActivity(addr.toString(), false, getGroupName(), addr);
					}
				}
			}
		}
		
		// this loop checks if there are nodes to delete
		iter1 = olist.iterator();
		while ( iter1.hasNext() )
		{
			Address addr = iter1.next();
			
			boolean flag = false;
			Iterator<Address> iter2 = list.iterator();
			while ( iter2.hasNext() )
			{
				Address addr2 = iter2.next();
				if ( addr.compareTo(addr2) == 0 )
				{
					// can leave this node alone
					flag = true;
					break;
				}				
			}
			if ( flag == false )
			{
				if ( getChan().getAddress().compareTo(addr) != 0 )
				{
					nodeRanker.deleteNodeIfSameGroup(addr.toString(), getGroupName());
				}
			}
		}
		
		// update the view
		this.view = view;
	}

}
